标准 IO

缓冲类型

标准 I/O 库中有三种缓冲概念:

缓冲类型行为

全缓冲

当缓冲区满时才执行 I/O 操作

行缓冲

遇到换行符或缓冲区满时执行 I/O 操作

无缓冲

立即执行 I/O 操作

使用 flush 函数可以立即刷新流,其具有以下特点:

  • 终端驱动程序中的 flush 表示丢弃缓冲区的内容

  • 当尝试从一个不带缓冲或行缓冲的流中输入数据时,会刷新所有 行缓冲

ISO C 做出了对缓冲特征的部分规定:

  • 当且仅当 stdin 和 stdout 不指向交互式设备时,它们才是全缓冲的

  • stderr 绝对不会是全缓冲的

下面的特征是实现定义的:

  • stderr 是无缓冲的

  • 指向终端的 stdin 和 stdout 是行缓冲的,否则为全缓冲

在多线程中缓冲可以会带来一些问题,以下面的代码为例:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
   printf("hello, world\n");
   fork();
   return 0;
}

在终端中直接执行的时候输出为一行内容,重定向到文件后输出为两行。原因是 stdio 默认是行缓冲的,而重定向到文件后,fork会把父进程缓冲区的内容也复制一份,在进程终止时被强制执行了缓冲区刷新

记录锁

记录锁允许进程对文件的一部分进行锁定,锁定的方式是读写锁。但是与读写锁不同的是,读写锁是用于线程同步的,加锁后如果想再加锁必须先解除以前的锁,而记录锁后面加的锁会解除前面的锁,另外

  • 进程间的记录锁遵循读写锁的共享互斥原则

  • 单一进程的新锁会覆盖旧锁

  • 在文件上加锁时,系统对加锁区域根据条件进行合并或者分裂

记录锁的 API 如下:

int fcntl(int fd, int cmd, flock* flockptr);

参数说明如下:

flock 是一个结构体,其定义为:

struct flock{
   short l_type;
   short l_whence;
   off_t l_start;
   off_t l_len;
   pid_t l_pid;
};

数据成员描述如下:

成员名取值说明

l_type

F_RDLCK

读锁

F_WRLCK

写锁

F_UNLCK

解锁

l_whence

SEEK_SET

SEEK_CUR

当前位置

SEEK_END

文件末尾

l_start

指针偏移量

l_len

要锁定区域的长度

l_pid

进程 PID 为 l_pid 的进程持有的锁能阻塞当前进程

其中 l_pid 是一个输出变量,当 cmd = F_GETLK 时得到此变量,l_pid 代表了持有锁的进程(也是导致当前进程无法获得锁的进程)

当 l_len == 0 时,则表示从 l_whence + l_start 之后的所有区域,因此锁定一个文件的方法是另 l_whence = SEEK_SET, l_start = 0, l_len = 0

另外,锁可以越过文件末尾,但是无法超过开头(因为文件允许有空洞)

在使用 SEEK_END 时要额外注意,因为 SEEK_END 指向的始终是文件的末尾,你每次修改数据文件的末尾都会发生变化,相应地,文件指针的偏移量 l_start 也一直在变化

参数 cmd 的取值为:

取值说明

F_GETLK

根据 flockptr 测试是否可以加锁,若可以加锁,则将 flockptr→l_type 设置为 F_UNLCK,否则将已有锁的信息复制到 flockptr

F_SETLK

根据 flockptr 尝试得到锁,若失败,则 fcntl 立即返回,并设 error 为 EAGAIN

F_SETLK W (wait)

根据 flockptr 尝试得到锁,若无法得到锁则阻塞至成功。若 flockptr→l_type == F_UNLCK,则表示清除 flockptr 代表的锁

实际上 F_SETLK 的错误返回在规范中允许 error == EAGAIN 或 error == EACCESS,但是在实现中,总是令 error == EAGAIN

另外,记录锁还有一些其它性质:

  • 锁的生命周期取进程和文件生命周期的较小者。也就是说如果不手动释放锁,那么锁要么在文件关闭时被释放,要么在进程关闭时被释放

  • 子进程不继承父进程的锁

  • 在执行 exec 后,新程序继承原程序的锁

建议锁和强迫锁

如果我们的进程全部通过数据库进程访问数据,那么我们只需要在数据库进程上加锁就行了,但是外部程序可以自由地修改数据,这种锁称为建议锁

如果锁是由操作系统保证的,用户加完锁后其它用户的任意进程无法修改数据,就说这个锁是强迫锁

尽管操作系统会对文件加锁,但是不会对 unlink 进行限制,因此可以通过删除原有文件再创建新文件的方式绕过强迫锁

如果多个进程同时对一个不加锁的文件进行操作,那么文件的内容取决于最后退出的程序

文件锁

相比记录锁而言,文件锁更加简单易用:

int lockf(int fd, int cmd, off_t len);
int flock(int fd, int operation);

flock 和 lockf 不同之处在于 flock 是一个建议锁。对于 lockf 而言,fd 指明了需要操作的文件描述符,cmd 指明了操作,而 len 指明了从当前文件指针开始锁定的字节数量

cmd 可为:

参数说明

F_LOCK

为文件的一段加锁

F_TLOCK

尝试加锁

F_ULOCK

解锁

F_TEST

测试锁,如果文件中被测试的部分没有锁定或者是调用进程持有锁就返回 0;如果是其它进程持有锁就返回 -1,并且 errno 设置为 EAGAIN 或 EACCES。

文件锁一个可用的操作是进程之间通过创建锁文件来相互通信。在包管理器中经常会有这个用法

文件描述符和文件指针的相互转换

文件描述符转文件指针

FILE *fdopen(int fd, const char *mode);

文件指针转文件描述符:

int fileno(FILE *stream);
Last moify: 2024-11-25 09:35:02
Build time:2025-07-18 09:41:42
Powered By asphinx